import { Propagation } from "./Propagation"
import { Isolation } from "./Isolation"
import { DataSource } from "./DataSource"
import { TransactionManager } from "./TransactionManager"
import { CurdTemplate } from "./CurdTemplate"
import { TransactionContext } from "./TransactionContext"
import { SQLException } from "./SQLException"

export class DbHolder {
  private static instance: DbHolder

  public static getInstance(): DbHolder {
    if (!DbHolder.instance) {
      DbHolder.instance = new DbHolder()
    }

    return DbHolder.instance
  }

  private dataSource: DataSource
  private transactionManager: TransactionManager
  private curdTemplate: CurdTemplate
  private transactionContext: TransactionContext

  public async initDb(mysqlConfig) {
    this.dataSource = new DataSource(mysqlConfig)
    this.transactionManager = new TransactionManager(this.dataSource)
    this.curdTemplate = new CurdTemplate(this.transactionManager)
    this.transactionContext = TransactionContext.getInstance()
  }

  public getCurdTemplate(): CurdTemplate {
    return this.curdTemplate
  }

  public async transaction(
    callback: () => Promise<any>,
    propagation: Propagation = Propagation.REQUIRED,
    isolation: Isolation = Isolation.REPEATABLE_READ): Promise<any> {
    const runOriginal = async () => {
      // console.log(`runOriginal `, callback.name)
      return await callback()
    }
    const runWithNewTransaction = async () => {
      // console.log(`runWithNewTransaction `, callback.name)
      const transactionCallback = async () => {
        const transactionInfo = this.transactionContext.getConnectionInfo()
        const { connection } = transactionInfo
        try {
          // TODO 需要这个吗
          // await connection.query('SET AUTOCOMMIT = 0')
          if (isolation != Isolation.DEFAULT) {
            await connection.query(`SET TRANSACTION ISOLATION LEVEL ${isolation}`)
          }
          // MySQL <==> await connection.query('START TRANSACTION')
          await connection.beginTransaction()
          // console.log(`Transaction begin`, callback.name)
          const result = await callback()
          // MySQL <==> await connection.query('COMMIT')
          await connection.commit()
          // console.log(`Transaction commit`, callback.name)
          return result
        } catch (e) {
          try {
            // MySQL <==> await connection.query('ROLLBACK');
            await connection.rollback()
          } catch (e) {
          }
          throw e
        } finally {
          if (connection) {
            // 放回连接池
            connection.release()
          }
        }
      }

      return await this.transactionContext.runInNewHookContext(transactionCallback)
    }

    const transactionInfo = this.transactionContext.getConnectionInfo()
    // console.log(`wrapped propagation = `, propagation)
    switch (propagation) {
      case Propagation.MANDATORY:
        if (transactionInfo == null) {
          throw new SQLException(
            "No existing transaction found for transaction marked with propagation 'MANDATORY'"
          )
        }
        return await runOriginal()
      case Propagation.NESTED:
        return await runWithNewTransaction()
      case Propagation.NEVER:
        return await runOriginal()
      case Propagation.NOT_SUPPORTED:
        return await runOriginal()
      case Propagation.REQUIRED:
        if (transactionInfo == null) {
          return await runWithNewTransaction()
        }
        return await runOriginal()
      case Propagation.REQUIRES_NEW:
        return await runWithNewTransaction()
      case Propagation.SUPPORTS:
        return await runOriginal()
    }
  }
}
